﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AutomationMLMapperViewModel.cs">
//   J. Prinz
// </copyright>
// <summary>
//   Main View Model of the Mapping Tool
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace AutomationMLMapper
{
    using AutomationMLMapper.MVVM_Framework;
    using AutomationMLMapper.Properties;
    using AutomationMLMapper.Utilities;
    using AutomationMLMapper.ViewModels.CAEXObjectCollectionViewModels;
    using AutomationMLMapper.ViewModels.CAEXObjectViewModels;
    using AutomationMLMapper.ViewModels.PaneViewModels;
    using AutomationMLMapper.ViewModels.SystemViewModels;
    using Microsoft.Win32;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Threading;

    /// <summary>
    ///   Main View Model of the Mapping Tool
    /// </summary>
    public class AutomationMLMapperViewModel : PropertyChangedBase
    {
        #region Fields

        /// <summary>
        ///   Instances of FileSystemWatcher
        /// </summary>
        private readonly Dictionary<string, FileSystemWatcher> watcherInstances;

        /// <summary>
        ///   <see cref="Export"/>
        /// </summary>
        private SourceSystemViewModel export;

        /// <summary>
        ///   <see cref="Import"/>
        /// </summary>
        private TargetSystemViewModel import;

        /// <summary>
        ///   <see cref="LaunchAML"/>
        /// </summary>
        private RelayCommand<object> launchAML;

        /// <summary>
        ///   <see cref="LoadSystemCommand"/>
        /// </summary>
        private RelayCommand<object> loadSystemCommand;

        /// <summary>
        ///   <see cref="SaveSystemCommand"/>
        /// </summary>
        private RelayCommand<object> saveSystemCommand;

        /// <summary>
        ///   <see cref="SystemSourceDocumentLocations"/>
        /// </summary>
        private RecentFileList systemSourceDocumentLocations;

        /// <summary>
        ///   <see cref="SystemTargetDocumentLocations"/>
        /// </summary>
        private RecentFileList systemTargetDocumentLocations;

        #endregion Fields

        #region Constructors and Destructors

        /// <summary>
        /// Initializes static members of the <see cref="AutomationMLMapperViewModel"/> class.
        /// static constructor creates an instance of UndoRedo Manager
        /// </summary>
        static AutomationMLMapperViewModel()
        {
            UndoRedo = new UndoRedoManager();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutomationMLMapperViewModel"/> class.
        /// Constructor for the Mapping Tool View Model creates a ViewModel for the Source System and one for the Target System
        /// </summary>
        public AutomationMLMapperViewModel()
        {
            this.Export = new SourceSystemViewModel { SystemType = Resources.ExportSystem, MainViewModel = this };
            this.Import = new TargetSystemViewModel { SystemType = Resources.ImportSystem, MainViewModel = this };

            this.OpenAutomationMLFiles = new CAEXFileCollectionViewModel();
            this.OpenAutomationMLFiles.Elements.CollectionChanged += this.OpenAutomationMLFilesCollectionChanged;
            this.watcherInstances = new Dictionary<string, FileSystemWatcher>();

            this.systemTargetDocumentLocations = Settings.Default.RecentTargetSystemDocuments;
            this.systemSourceDocumentLocations = Settings.Default.RecentSourceSystemDocuments;

            this.RoleLibsCollection = new CAEXObjectCollectionViewModel<RoleClassLibViewModel>(null);
        }

        #endregion Constructors and Destructors

        #region Public Properties

        /// <summary>
        ///  Collection of RoleClassLibraries of the Export and Import - System
        /// </summary>
        public CAEXObjectCollectionViewModel<RoleClassLibViewModel> RoleLibsCollection { get; set; }

        /// <summary>
        ///  Singleton Instance of the MainViewModel
        /// </summary>
        public static AutomationMLMapperViewModel MainViewModel;

        private ObservableCollection<PaneViewModel> _tools = new ObservableCollection<PaneViewModel>();
        private ReadOnlyObservableCollection<PaneViewModel> _readonyTools = null;

        /// <summary>
        /// AutomationML Tools which are currently viewed as Layout Elements
        /// </summary>
        public ReadOnlyObservableCollection<PaneViewModel> AutomationMLTools
        {
            get
            {
                if (_readonyTools == null)
                    _readonyTools = new ReadOnlyObservableCollection<PaneViewModel>(_tools);

                return _readonyTools;
            }
        }

        /// <summary>
        /// Add a ViewModel to the Tools-Collection
        /// </summary>
        /// <param name="viewModel">a PaneViewModel</param>
        public void AddAutomationMLTool(PaneViewModel viewModel)
        {
            _tools.Add(viewModel);
        }

        /// <summary>
        /// Remove a ViewModel from the Tools-Collection
        /// </summary>
        /// <param name="viewModel">a PaneViewModel</param>
        public void RemoveAutomationMLTool(PaneViewModel viewModel)
        {
            _tools.Remove(viewModel);
        }

        /// <summary>
        ///   The Instance of the global UndoRedo Manager
        /// </summary>
        public static UndoRedoManager UndoRedo { get; set; }

        /// <summary>
        ///   The Active System is the Source- or Target-System which is currently visible in the UI
        /// </summary>
        public SystemViewModel ActiveSystem { get; set; }

        /// <summary>
        ///   Export View Model is a <see cref="SourceSystemViewModel" />
        /// </summary>
        public SourceSystemViewModel Export
        {
            get
            {
                return this.export;
            }

            set
            {
                this.export = value;
                this.NotifyOfPropertyChange();
                if (this.export != null)
                    this.Export.RoleLibsCollection.Elements.CollectionChanged += new NotifyCollectionChangedEventHandler(RoleLibs_CollectionChanged);
            }
        }

        /// <summary>
        ///   Import View Model is a <see cref="TargetSystemViewModel" />
        /// </summary>
        public TargetSystemViewModel Import
        {
            get
            {
                return this.import;
            }

            set
            {
                this.import = value;
                this.NotifyOfPropertyChange();

                if (this.import != null)
                    this.Import.RoleLibsCollection.Elements.CollectionChanged += new NotifyCollectionChangedEventHandler(RoleLibs_CollectionChanged);
            }
        }

        /// <summary>
        ///   Launch the AML-Editor for a SystemModel (System is specified via Command Parameter)
        /// </summary>
        public ICommand LaunchAML
        {
            get
            {
                return this.launchAML
                       ?? (this.launchAML = new RelayCommand<object>(LaunchAMLEditor, CanLaunchAMLEditor));
            }
        }

        /// <summary>
        ///   A Load SystemCommand for a selected recentFile Item in the MainView
        /// </summary>
        public ICommand LoadSystemCommand
        {
            get
            {
                return this.loadSystemCommand
                       ??
                       (this.loadSystemCommand =
                        new RelayCommand<object>(this.LoadSystemCommandExecute, LoadSystemCommandCanExecute));
            }
        }

        /// <summary>
        ///   we need this to get the Dispatcher Object for async methods, called from the FileSystemWatcher EventHandler
        /// </summary>
        public AutomationMLMapperMain MainView { get; set; }

        /// <summary>
        ///   The AutomationML Files which are currently open
        /// </summary>
        public CAEXFileCollectionViewModel OpenAutomationMLFiles { get; set; }

        /// <summary>
        ///   Gets a value indicating whether recent source systems exist.
        /// </summary>
        public bool RecentSourceSystemsExist
        {
            get
            {
                return this.SystemSourceDocumentLocations.RecentFiles.Count > 0;
            }
        }

        /// <summary>
        ///   Gets a value indicating whether recent target systems exist.
        /// </summary>
        public bool RecentTargetSystemsExist
        {
            get
            {
                return this.SystemTargetDocumentLocations.RecentFiles.Count > 0;
            }
        }

        /// <summary>
        ///   The AutomationML File for the System is saved to disk (System is specified via Command Parameter)
        /// </summary>
        public ICommand SaveSystemCommand
        {
            get
            {
                return this.saveSystemCommand
                       ??
                       (this.saveSystemCommand =
                        new RelayCommand<object>(this.SaveSystemCommandExecute, SaveSystemCommandCanExecute));
            }
        }

        /// <summary>
        ///   Gets the system source document locations.
        /// </summary>
        public RecentFileList SystemSourceDocumentLocations
        {
            get
            {
                if (this.systemSourceDocumentLocations == null)
                {
                    this.systemSourceDocumentLocations = new RecentFileList { MaxFilesNumber = 8 };
                    Settings.Default.RecentSourceSystemDocuments = this.systemSourceDocumentLocations;
                }

                return this.systemSourceDocumentLocations;
            }
        }

        /// <summary>
        ///   Gets the system target document locations.
        /// </summary>
        public RecentFileList SystemTargetDocumentLocations
        {
            get
            {
                if (this.systemTargetDocumentLocations == null)
                {
                    this.systemTargetDocumentLocations = new RecentFileList { MaxFilesNumber = 8 };
                    Settings.Default.RecentTargetSystemDocuments = this.systemTargetDocumentLocations;
                }

                return this.systemTargetDocumentLocations;
            }
        }

        #endregion Public Properties

        #region Methods

        /// <summary>
        /// DirPath of a FilePath
        /// </summary>
        /// <param name="filePath">
        /// File Path
        /// </param>
        /// <returns>
        /// The DirectoryName for the FilePath
        /// </returns>
        private static string DirPath(string filePath)
        {
            return !string.IsNullOrEmpty(filePath) ? Path.GetDirectoryName(filePath) : string.Empty;
        }

        /// <summary>
        /// Launch registered Standardprogram for editing AML-Files.
        /// </summary>
        /// <param name="parameter">
        /// Filename provided as Command-Parameter
        /// </param>
        private static void LaunchAMLEditor(object parameter)
        {
            var filePath = parameter as string;
            ProgrammLauncher.Launch(filePath);
        }

        /// <summary>
        /// Test, if the <see cref="LoadSystemCommand"/> can execute
        /// </summary>
        /// <param name="parameter">
        /// The Parameter is expected to be a <see cref="RecentFile"/>
        /// </param>
        /// <returns>
        /// true, if ready for execution (if the expected parameter object is provided).
        /// </returns>
        private static bool LoadSystemCommandCanExecute(object parameter)
        {
            return parameter is RecentFile;
        }

        /// <summary>
        /// Test, if <see cref="LaunchAMLEditor"/> can execute.
        /// </summary>
        /// <param name="parameter">
        /// Paramter is expected to be a Filename.
        /// </param>
        /// <returns>
        /// true, if provided Filename is an existing File
        /// </returns>
        private static bool CanLaunchAMLEditor(object parameter)
        {
            return parameter is string && !string.IsNullOrEmpty(parameter as string) && File.Exists(parameter as string);
        }

        /// <summary>
        /// Test, if the <see cref="SaveSystemCommand"/> can execute
        /// </summary>
        /// <param name="parameter">
        /// paramter is expected to be a ViewModel of the System <see cref="SystemViewModel"/>
        /// </param>
        /// <returns>
        /// true, if the System has a loaded AmlDocumentRepresentation <see cref="SystemViewModel.AmlDocumentRepresentation"/>
        /// </returns>
        private static bool SaveSystemCommandCanExecute(object parameter)
        {
            SystemViewModel system = null;

            if (parameter is SystemViewModel)
            {
                system = parameter as SystemViewModel;
            }

            return system != null && system.AmlDocumentRepresentation != null && !string.IsNullOrEmpty(system.Name);

            // && !ActiveSystem.HasErrors;
        }

        /// <summary>
        /// Handler for change events, raised by an instance of the FileSystemWatcher
        /// </summary>
        /// <param name="sender">
        /// An Instance of FileSystemWatcher
        /// </param>
        /// <param name="e">
        /// Event Arguments of FileSystemWatcher
        /// </param>
        private void AmlWatcherChanged(object sender, FileSystemEventArgs e)
        {
            try
            {
                var fileSystemWatcher = sender as FileSystemWatcher;
                if (fileSystemWatcher != null)
                {
                    fileSystemWatcher.EnableRaisingEvents = false;
                }

                var pathChanged = this.OpenAutomationMLFiles.Elements.FirstOrDefault(f => (f.FilePath == e.FullPath));
                if (pathChanged != null)
                {
                    var result = MessageBox.Show(
                        pathChanged + " has been changed externaly! Do you want to reload it?",
                        "External Changes",
                        MessageBoxButton.YesNo,
                        MessageBoxImage.Question);
                    if (result == MessageBoxResult.Yes)
                    {
                        pathChanged.SystemViewModel.ReLoad();
                    }
                }
            }
            finally
            {
                var fileSystemWatcher = sender as FileSystemWatcher;
                if (fileSystemWatcher != null)
                {
                    fileSystemWatcher.EnableRaisingEvents = true;
                }
            }
        }

        /// <summary>
        /// Handler for Change Event of the "FilePath" Property of a CAEXFile, the handler updates the FileSystemWatcher
        /// Instances.
        /// </summary>
        /// <param name="sender">
        /// A Representation-Object<see cref="CAEXFileTypeViewModel"/>of a CaexFile
        /// </param>
        /// <param name="e">
        /// Event Argument
        /// </param>
        private void CAEXFilePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "FilePath")
            {
                this.UpdateAllWatcherInstances();
                var caexFileTypeViewModel = sender as CAEXFileTypeViewModel;
                if (caexFileTypeViewModel != null)
                {
                    this.CreateWatcherInstanceForFile(caexFileTypeViewModel.FilePath);
                }
            }
        }

        /// <summary>
        /// create an instance of FileSystemWatcher for the Directory of the FilePath
        /// </summary>
        /// <param name="filePath">
        /// FilePath to watch
        /// </param>
        private void CreateWatcherInstanceForFile(string filePath)
        {
            var dirPath = DirPath(filePath);
            if (Directory.Exists(dirPath))
            {
                if (!this.watcherInstances.ContainsKey(dirPath))
                {
                    this.watcherInstances.Add(
                        dirPath,
                        new FileSystemWatcher
                            {
                                Path = dirPath,
                                Filter = "*.aml",
                                IncludeSubdirectories = false,
                                EnableRaisingEvents = true,
                                NotifyFilter = NotifyFilters.LastWrite
                            });

                    // the change event has to be handled on the UI-Thread of the MainView
                    this.watcherInstances[dirPath].Changed +=
                        (obj, e) =>
                        this.MainView.Dispatcher.Invoke(
                            DispatcherPriority.Send, new Action(() => this.AmlWatcherChanged(obj, e)));
                }
            }
        }

        /// <summary>
        /// The Collection of RoleLibraries of a System has changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RoleLibs_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Reset:
                    if (e.OldItems != null)
                        foreach (RoleClassLibViewModel roleClassLibrary in e.OldItems)
                            if (this.RoleLibsCollection.Elements.Contains(roleClassLibrary))
                                this.RoleLibsCollection.Elements.Remove(roleClassLibrary);
                    break;

                case NotifyCollectionChangedAction.Add:
                    {
                        RoleClassLibViewModel roleClassLibrary = (RoleClassLibViewModel)e.NewItems[0];
                        SystemViewModel owner = roleClassLibrary.Owner;

                        var partnerRoleClassLibrary = this.RoleLibsCollection.Elements.SingleOrDefault(lib => lib.Name == roleClassLibrary.Name);

                        if (partnerRoleClassLibrary != null)
                        {
                            // a roleLib is already registred
                            if (!partnerRoleClassLibrary.Owner.Equals(owner))
                            {
                                partnerRoleClassLibrary.PartnerLibrary = roleClassLibrary;
                                roleClassLibrary.PartnerLibrary = partnerRoleClassLibrary;
                            }

                            if (partnerRoleClassLibrary.Owner.Equals(Export))
                                partnerRoleClassLibrary.CompareWithPartner();
                            else
                                roleClassLibrary.CompareWithPartner();

                            // the user perspective should always look from the importing system on the Role Class Library
                            // we eventually have to exchange the Libraries in the collection

                            if (partnerRoleClassLibrary.Owner == Import)
                            {
                                this.RoleLibsCollection.Elements.Remove(partnerRoleClassLibrary);
                                this.RoleLibsCollection.Elements.Add(roleClassLibrary);
                            }
                        }
                        else
                        {
                            // a roleLib is not registred
                            this.RoleLibsCollection.Elements.Add(roleClassLibrary);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Execution Action of <see cref="LoadSystemCommand"/>
        /// </summary>
        /// <param name="parameter">
        /// Recent File
        /// </param>
        private void LoadSystemCommandExecute(object parameter)
        {
            var recentFile = parameter as RecentFile;
            if (recentFile != null)
            {
                if (this.SystemSourceDocumentLocations.RecentFiles.Contains(recentFile))
                {
                    this.Export.LoadSystemWithPath(recentFile.Path);
                }
                else
                {
                    this.Import.LoadSystemWithPath(recentFile.Path);
                }
            }
        }

        /// <summary>
        /// Handler for Collection Change Event of the CAEXFile Collection updates the FileSystemWatcher Instances
        /// </summary>
        /// <param name="sender">
        /// A Collection Object, containing Representation objects <see cref="CAEXFileTypeViewModel"/> of CAEXFiles
        /// </param>
        /// <param name="e">
        /// Collection Change Event Argument
        /// </param>
        private void OpenAutomationMLFilesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (CAEXFileTypeViewModel caexFile in e.NewItems)
                    {
                        this.CreateWatcherInstanceForFile(caexFile.FilePath);
                        caexFile.PropertyChanged += this.CAEXFilePropertyChanged;

                        if (caexFile.SystemViewModel is SourceSystemViewModel)
                        {
                            this.SystemSourceDocumentLocations.AddFile(caexFile.FilePath);
                            this.NotifyOfPropertyChange();
                        }
                        else
                        {
                            this.SystemTargetDocumentLocations.AddFile(caexFile.FilePath);
                            this.NotifyOfPropertyChange();
                        }
                    }

                    break;

                case NotifyCollectionChangedAction.Remove:
                    this.UpdateAllWatcherInstances();
                    foreach (CAEXFileTypeViewModel caexFile in e.OldItems)
                    {
                        caexFile.PropertyChanged -= this.CAEXFilePropertyChanged;
                    }

                    break;
            }
        }

        /// <summary>
        /// The Execution Action of the <see cref="SaveSystemCommand"/>
        /// </summary>
        /// <param name="parameter">
        /// paramter is expected to be a ViewModel of the System <see cref="SystemViewModel"/>
        /// </param>
        private void SaveSystemCommandExecute(object parameter)
        {
            var system = parameter as SystemViewModel;

            if (system != null && system.AmlDocumentRepresentation != null)
            {
                var sfd = new SaveFileDialog
                    {
                        Filter = "AutomationML  (*.aml)|*.aml",
                        Title = "System Definition - AutomationML File",
                        FileName = system.Name,
                        DefaultExt = "aml"
                    };

                // sfd.CreatePrompt = true;
                // sfd.OverwritePrompt = true;
                var result = sfd.ShowDialog();
                if (result.Value)
                {
                    var dirPath = DirPath(sfd.FileName);
                    if (this.watcherInstances.ContainsKey(dirPath))
                    {
                        this.watcherInstances[dirPath].EnableRaisingEvents = false;
                        system.AmlDocumentRepresentation.SaveToFile(sfd.FileName);
                        this.watcherInstances[dirPath].EnableRaisingEvents = true;
                    }
                    else
                    {
                        system.AmlDocumentRepresentation.SaveToFile(sfd.FileName);
                    }
                }
            }
        }

        /// <summary>
        ///   Check All Instances of FileSystemWatcher for corresponding Open CaexFiles
        /// </summary>
        private void UpdateAllWatcherInstances()
        {
            foreach (var watcher in
                this.watcherInstances.Where(
                    w => this.OpenAutomationMLFiles.Elements.All(p => DirPath(p.FilePath) != w.Key)).ToList())
            {
                this.watcherInstances.Remove(watcher.Key);
            }
        }

        #endregion Methods
    }
}